import random
import json
import collections
import logging
from datetime import datetime

from django.core import serializers
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import render, redirect, get_object_or_404
from django.template import RequestContext
from django.utils import simplejson
from django.db import transaction
from account.decorators import staff_required

from page.models import Page
from page.utils import get_active_diagnostic_test
from question.models import Question, Answer
from test.models import Test, ProgressCheck, DiagnosticTest
from test.models import ActiveDiagnosticTest, Test
from engonline.models import UserAnswer
from tree_utils import remove_page_from_tree, insert_page, get_root_pages, find_next, find_previous, tree_to_set, save_tree, clear_tree_caches

log = logging.getLogger(__name__)


# List all the pages (in a tree structure)
@login_required
@staff_required
def page(request):
    log.debug("test")
    page = Page.objects.filter(trash=False)
    return render(request, 'page.html', {'page': page})


@login_required
def all_pages(request, parent_id=None):
    """
    Get all pages as json, id and title.
    """
    tests = Test.objects.all()
    pages = Page.objects.exclude(id__in=tests)  # Exclude all tests from the pages

    data = serializers.serialize('json', pages, fields=('title', 'slug_title'))

    return HttpResponse(data, content_type="application/json")


@login_required
@staff_required
def create(request, parent_id=None):
    """
    Create a new page.
    """
    pages = Page.objects.all()

    if request.method == 'POST':
        text = request.POST['editor']
        title = request.POST['title']

        # if the new page is missing a title
        if title == "":
            return render(request, 'create.html', {'parent_id': parent_id, 'pages': pages, 'missing_title': True})

        # Tries to put the page under its parent page
        try:
            parent_page = Page.objects.get(id=parent_id)
            page = Page(title=title, content=text,
                        creator=request.user, changer=request.user)
            page.save()
            insert_page(parent_page, page)
            request.session['page_saved'] = True

        # If the there is no parent, add it as the last root-sibling
        except Page.DoesNotExist:
            page = Page(title=title, content=text, creator=request.user,
                        changer=request.user)
            page.save()
            insert_page(None, page)

        return redirect('page.views.restructure')

    else:
        return render(request, 'create.html', {'parent_id': parent_id, 'pages': pages})


@login_required
@staff_required
def edit(request, page_id):
    """
    Edit a page.
    """
    # If POST, save new attributes.
    # POST is done by AJAX
    if request.method == 'POST':
        text = request.POST['editor']
        title = request.POST['title']
        page = get_object_or_404(Page, id=page_id)

        # If no title is sent, return error.
        if title == "":
            return HttpResponseBadRequest("missing_title")

        page.content = text
        page.title = title
        page.changer = request.user
        page.save()
        # Clear tree cashe so the side menu gets updated
        clear_tree_caches()

        return HttpResponse("")
    # Else, show the edit page
    else:
        page_saved = request.session.get('page_saved', False)
        request.session['page_saved'] = False
        page = get_object_or_404(Page, id=page_id)
        return render(request, 'create.html', {'page': page, 'edit_page': page, 'page_saved': page_saved},
                      context_instance=RequestContext(request))


@login_required
@staff_required
def trash_page(request,  page_id):
    """
    Set the trash attribute to true and removes the page from the tree
    """
    try:
        page = Page.objects.get(id=page_id)
        remove_page_from_tree(page)
        page.trash = True
        page.save()
        
        return redirect('page.views.page')
    
    except Page.DoesNotExist:
        return redirect('page.views.page')

@login_required
def view_page(request, slug_title):
    """
    Display a page
    """
    page = get_object_or_404(Page, slug_title=slug_title)
    next = find_next(page)
    prev = find_previous(page)

    if page.type() is Page:
        return render(request, 'view_page.html', {'page': page, 'next': next, 'prev': prev})
    else:
        return _view_test(request, slug_title)

def _view_test(request, slug_title):
    """
    Display a test
    """
    # TODO:
    # Handle non JS POST

    test = get_object_or_404(Test, slug_title=slug_title)

    if request.method == "GET":
        return _view_test_get(request, test)
        
    elif request.method == "POST":
        return _view_test_post(request, test)

    else:
        return HttpResponseBadRequest()

def _view_test_get(request, test):
    next = find_next(test)
    prev = find_previous(test)
    test_type = "Exercise"
    questions_to_show = 0

    questions = test.questions.filter(
        trash=False).order_by('contains__question_order')

    
    if test.type() is ProgressCheck:

        test_type = "ProgressCheck"
        progress_check = test.progresscheck
        questions_to_show = progress_check.questions_to_show

        # Get a random subset of questions if test is a progress check
        if request.session.get('viewing_as_student', False) or not request.user.is_staff:
            
            # Make a list of all ids for the questions related to this test
            all_question_ids = list(questions.values_list(
                'id', flat=True))
            # Shuffle the list
            random.shuffle(all_question_ids)
            # Limit to the number of questions the test should contain
            valid_question_ids = all_question_ids[
                :questions_to_show]
            # Filter the QuerySet accordingly
            questions = questions.filter(id__in=valid_question_ids)
            
            
        
    
    elif test.type() is DiagnosticTest:
        test_type = "DiagnosticTest"
    
    # Save currently selected questions to access them efter POST
    request.session['questions'] = questions
    return render(request, 'view_test.html', {'page': test,
        'questions': questions, 'next': next, 'prev': prev, 
        'test_type':test_type,  'questions_to_show':questions_to_show})

def _view_test_post(request, test):
    postDict = request.POST.copy()
    date = datetime.now();
    # Retrieve the questions that were shown with GET
    questions = request.session.get('questions')
    
    # Remove CSRF token
    del postDict['csrfmiddlewaretoken']

    corrected_answers = {}
    for question in questions:

        # Save question/answer
        answer = postDict.get(str(question.id), '')
        UserAnswer.objects.create(
            date = date,
            user = request.user, 
            test = test, 
            question = question, 
            answer = answer)

        # Add if the answer is either correct or incorrect and the info
        # accompying that answer
        corrected_answers[question.id] = question.correct_answer(answer)

    return HttpResponse(json.dumps(corrected_answers), 
        content_type="application/json")

@login_required
@staff_required
def restructure(request):
    """
    !POST:
        Returns the restructure book view.
    POST:
        Tries to save the new book structure.
        Pages in the old tree which are not present in the new structure will be trashed.
        If the process is unsuccessful, all database changes are rolled back.
    """
    if request.method == 'POST':
        try:
            with transaction.commit_on_success():
                #Get the pages in the current tree
                old_tree_set = tree_to_set()

                #Try to save the new tree structure
                serialization = request.POST.get('serialized-structure')
                save_tree(serialization)

                #Get the pages in the new tree
                new_tree_set = tree_to_set()

                #TODO Refactor to method
                #Trash the pages missing from the new tree
                trashed_pages = list(old_tree_set - new_tree_set)
                for page_id in trashed_pages:
                    page = Page.objects.get(pk=page_id)
                    page.trash = True
                    page.changer = request.user                   
                    page.save()

                #Restore the pages which are trashed but in the new tree
                restored_pages = list(new_tree_set - old_tree_set)
                for page_id in restored_pages:
                    page = Page.objects.get(pk=page_id)
                    page.trash = False
                    page.changer = request.user
                    page.save()

                return HttpResponse(status=200)
        except:
            return HttpResponse(status=400)
    else:
        return render(request, 'restructure.html')


def take_diagnostic(request):
    """
    Take the diagnostic test
    """
    diagnostic_test = get_active_diagnostic_test()
    if diagnostic_test is not None:
        return _view_test(request, diagnostic_test.slug_title)
    else:
        #TODO handle no active diagnostic_test
        return page(request)

@login_required
@staff_required    
def set_questions_to_show(request):
    """
    Set number of questions to show in a progress check
    """
    if request.method == 'POST':
        page_id = request.POST['page_id']
        questions = request.POST['questions_to_show']
        pc = get_object_or_404(ProgressCheck, id=page_id)
        pc.questions_to_show = questions
        pc.save()
        return HttpResponseRedirect(pc.get_absolute_url())